<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Tools;

use ReflectionClass;
use ReflectionProperty;
use ReflectionException;
use Srv\Libs\Boot;
use Srv\Libs\Common\CommJson;
use Srv\Libs\Common\CommString;

class ReflectionClassData
{
    /**
     * @param string $className
     * @return array
     * getRefData
     */
    final public static function getRefData(string $className):array
    {
        $refClassData                   = [];
        $refClassData['name']           = $className;
        $refClassData['nameOnly']       = CommString::getClassOnlyName($className);
        $refClassData['comment']        = '';
        $refClassData['constantList']   = [];
        $refClassData['propertyList']   = [];
        try{
            $ReqClassNameReflectionClass    = new ReflectionClass($className);
            $classNameDocComment            = $ReqClassNameReflectionClass->getDocComment();
            if(is_string($classNameDocComment)) $refClassData['comment'] = $classNameDocComment;
            $ReflectionPropertyList         = $ReqClassNameReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
            $ReflectionConstantsList        = $ReqClassNameReflectionClass->getReflectionConstants();
        }catch(ReflectionException $ReflectionException){
            $ReflectionPropertyList         = [];
            $ReflectionConstantsList        = [];
        }
        if(count($ReflectionConstantsList) < 1 && count($ReflectionPropertyList) < 1) return $refClassData;
        // 解码文件格式的单行注释方式
        $propertyCommentList        = [];
        $classNameFilePath          = Boot::getClassFilePath($className);
        if(file_exists($classNameFilePath)){
            $classFileList              = @file($classNameFilePath);
            foreach ($classFileList as $classFileLine){
                $classFileLine          = trim($classFileLine);
                if(preg_match('/^(protected|public|private)\s+.*\s+\$(\S+)\s+=\s*\S+\s*\/\/(.*)$/', $classFileLine, $classMatch)){
                    $propertyCommentList[$classMatch[2]] = trim($classMatch[3]);
                }
                if(preg_match('/^(protected|public|private)\s+const\s+(\S+)\s+=\s*\S+\s*\/\/(.*)$/', $classFileLine, $classMatch)){
                    $propertyCommentList[$classMatch[2]] = trim($classMatch[3]);
                }
            }
        }
        if(count($ReflectionConstantsList) > 0) foreach ($ReflectionConstantsList as $ReflectionConstant){
            $constantName                   = $ReflectionConstant->getName();
            $constantDocComment             = $ReflectionConstant->getDocComment();
            if(!is_string($constantDocComment) || strlen($constantDocComment) < 1) $constantDocComment = $propertyCommentList[$constantName]??'';
            $refClassData['constantList'][$constantName] = ['comment' => $constantDocComment];
        }
        if(count($ReflectionPropertyList) > 0) foreach ($ReflectionPropertyList as $ReflectionProperty){
            $propertyName                   = $ReflectionProperty->getName();
            $propertyType                   = $ReflectionProperty->getType()->getName();
            $propertyDocComment             = $ReflectionProperty->getDocComment();
            if(!is_string($propertyDocComment) || strlen($propertyDocComment) < 1) $propertyDocComment = $propertyCommentList[$propertyName]??'';
            $refClassData['propertyList'][$propertyName] = ['comment' => $propertyDocComment, 'type' => $propertyType];
        }
        return $refClassData;
    }

    /**
     * @param string $classNameProto
     * @return array
     * getRefDataByProto
     */
    final public static function getRefDataByProto(string $classNameProto):array
    {
        $refClassData                   = [];
        $refClassData['name']           = $classNameProto;
        $refClassData['nameOnly']       = CommString::getClassOnlyName($classNameProto);
        $refClassData['comment']        = '';
        $refClassData['constantList']   = [];
        $refClassData['propertyList']   = [];
        try{
            $ReqClassNameReflectionClass    = new ReflectionClass($classNameProto);
            $classNameDocComment            = $ReqClassNameReflectionClass->getDocComment();
            if(is_string($classNameDocComment) && preg_match('#\*(.*)\s+.*\s+.*<code>#', $classNameDocComment, $match)) $refClassData['comment'] = $match[1];
            $ReflectionPropertyList         = $ReqClassNameReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
            $ReflectionConstantsList        = $ReqClassNameReflectionClass->getReflectionConstants();
        }catch(ReflectionException $ReflectionException){
            $ReflectionPropertyList         = [];
            $ReflectionConstantsList        = [];
        }
        if(count($ReflectionConstantsList) > 0) foreach ($ReflectionConstantsList as $ReflectionConstant){
            $constantName                   = $ReflectionConstant->getName();
            $constantDocComment             = $ReflectionConstant->getDocComment();
            if(is_string($constantDocComment) && preg_match('#\*(.*)\s+.*\s+.*<code>(\S+)\s+=\s+(\S+);</code>#', $constantDocComment, $match)){
                $comment    = $match[1];
                $value      = $match[3];
            }else{
                $comment    = '';
                $value      = '';
            }
            $refClassData['constantList'][$constantName] = ['comment' => $comment, 'value' => $value];
        }
        if(count($ReflectionPropertyList) > 0) foreach ($ReflectionPropertyList as $ReflectionProperty){
            $propertyName                   = $ReflectionProperty->getName();
            $propertyDocComment             = $ReflectionProperty->getDocComment();
            if(is_string($propertyDocComment) && preg_match('#\*(.*)\s+.*\s+.*<code>(\S+\s+)?(\S+)\s+\S+\s+=\s+(\d+);</code>#', $propertyDocComment, $match)){
                $comment    = $match[1];
                $flag       = strtolower(trim($match[2]));
                $type       = str_replace('.', '\\', trim($match[3], '.'));
                $index      = $match[4];
            }else{
                $comment    = '';
                $flag       = '';
                $type       = '';
                $index      = 0;
            }
            $refClassData['propertyList'][$propertyName] = ['type' => $type, 'comment' => $comment, 'index' => $index, 'flag' => $flag];
        }
        return $refClassData;
    }

    /**
     * @param string $classNameProto
     * @param string $dbName
     * @return string
     * getRefTableNameByProto
     */
    final public static function getRefTableNameByProto(string $classNameProto, string &$dbName):string
    {
        $tableName                          = '';
        $dbName                             = '';
        try{
            $ReqClassNameReflectionClass    = new ReflectionClass($classNameProto);
            $classNameDocComment            = $ReqClassNameReflectionClass->getDocComment();
            if(preg_match('/'.(strpos($classNameDocComment, '@') === false ? '&#64;' : '@').'(\[.*])/', $classNameDocComment, $matchTable)){
                $tableData      = CommJson::decodeArray($matchTable[1]);
                $tableName      = $tableData[0]??'';
                $dbName         = $tableData[1]??'';
            }
        }catch(ReflectionException $ReflectionException){
        }
        return $tableName;
    }

    /**
     * @param string $classNameProto
     * @param string $tableName
     * @param string $dbName
     * @return array
     * getRefFieldByProto
     */
    final public static function getRefFieldByProto(string $classNameProto, string &$tableName, string &$dbName):array
    {
        $tableName                          = '';
        $dbName                             = '';
        $propertyList                       = [];
        try{
            $ReqClassNameReflectionClass    = new ReflectionClass($classNameProto);
            $classNameDocComment            = $ReqClassNameReflectionClass->getDocComment();
            if(preg_match('/'.(strpos($classNameDocComment, '@') === false ? '&#64;' : '@').'(\[.*])/', $classNameDocComment, $matchTable)){
                $tableData      = CommJson::decodeArray($matchTable[1]);
                $tableName      = $tableData[0]??'';
                $dbName         = $tableData[1]??'';
            }
            $ReflectionPropertyList         = $ReqClassNameReflectionClass->getProperties(ReflectionProperty::IS_PROTECTED | ReflectionProperty::IS_PUBLIC | ReflectionProperty::IS_PRIVATE);
        }catch(ReflectionException $ReflectionException){
            $ReflectionPropertyList         = [];
        }
        if(count($ReflectionPropertyList) > 0) foreach ($ReflectionPropertyList as $ReflectionProperty){
            $propertyName                   = $ReflectionProperty->getName();
            $propertyDocComment             = $ReflectionProperty->getDocComment();
            if(is_string($propertyDocComment) && preg_match('#\*(.*)\s+.*\s+.*<code>(\S+\s+)?(\S+)\s+\S+\s+=\s+(\d+);</code>#', $propertyDocComment, $match)){
                $comment        = $match[1];
                $flag           = strtolower(trim($match[2]));
                $type           = str_replace('.', '\\', trim($match[3], '.'));
                $index          = $match[4];
                $pri            = strpos($comment, '"PRI"') !== false ? 1 : 0;
                $uni            = strpos($comment, '"UNI"') !== false ? 1 : 0;
                $mul            = strpos($comment, '"MUL"') !== false ? 1 : 0;
                $columnName     = '';
                $columnTypeStr  = '';
                $columnType     = '';
                $unsigned       = 0;
                if(preg_match('/'.(strpos($comment, '@') === false ? '&#64;' : '@').'(\[.*])/', $comment, $matchField)){
                    $columnKeys     = CommJson::decodeArray($matchField[1]);
                    $columnName     = $columnKeys[0]??'';
                    $columnTypeStr  = $columnKeys[1]??'';
                    if(strlen($columnTypeStr) > 0){
                        $unsigned   = strpos($columnTypeStr, 'unsigned') !== false ? 1 : 0;
                        if(preg_match('/(^[a-zA-Z]+)/', $columnTypeStr, $columnTypeMatch)) $columnType = strtolower(trim($columnTypeMatch[1]??''));
                    }
                }
            }else{
                $comment        = '';
                $flag           = '';
                $type           = '';
                $index          = 0;
                $pri            = 0;
                $uni            = 0;
                $mul            = 0;
                $columnName     = '';
                $columnTypeStr  = '';
                $columnType     = '';
                $unsigned       = 0;
            }
            $propertyList[$propertyName] = ['name' => $propertyName, 'type' => $type, 'comment' => $comment, 'index' => $index, 'flag' => $flag, 'columnName' => $columnName, 'columnTypeStr' => $columnTypeStr, 'columnType' => $columnType, 'unsigned' => $unsigned, 'pri' => $pri, 'uni' => $uni, 'mul' => $mul];
        }
        return $propertyList;
    }

    /**
     * @param string $columnTypeStr
     * @param string $columnType
     * @param int $columnTypeLength
     * @param int $columnTypePrecision
     * @param int $columnTypeScale
     * @return string
     * getProtoTypeByDb
     */
    final public static function getProtoTypeByDb(string $columnTypeStr, string $columnType, int $columnTypeLength, int $columnTypePrecision, int $columnTypeScale):string
    {
        unset($columnTypeLength, $columnTypePrecision, $columnTypeScale);
        $columnType     = trim($columnType);
        switch (strtolower($columnType)){
            case 'bit':
            case 'tinyint':
            case 'smallint':
            case 'mediumint':
            case 'int':
            case 'timestamp':{
                return strpos($columnTypeStr, 'unsigned') !== false ? 'uint32' : 'int32';
            }
            case 'serial':
            case 'bigint': {
                return strpos($columnTypeStr, 'unsigned') !== false ? 'uint64' : 'int64';
            }
            case 'decimal':
            case 'float':
            case 'double':
            case 'real':{
                return 'float';
            }
            case 'boolean':{
                return 'bool';
            }
            default:{
                return 'string';
            }
        }
    }
}
